/*
 * Copyright 2021 Scorpiour
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
*/

package cc.bestroute.halloween;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import java.util.HashMap;

public class PriorityTaskDispatcher {

    private final Map<Integer, Executor> executorMap = new HashMap<>();
    private final List<BlockingQueue<Function<Void,Void>>> queueList = new ArrayList<>();

    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();

    private volatile Boolean closed = false;

    public PriorityTaskDispatcher() throws InstantiationException {
        this.init();
    }

    private void init() throws InstantiationException {
        System.out.println("CPU COUNT: " + CPU_COUNT);
        if (CPU_COUNT != 4) {
            this.initCustomSize();
        } else {
            this.initNormalSize();
        }
    }

    private void initCustomSize() throws InstantiationException {
        // init queues
        List<BlockingQueue<Function<Void, Void>>> queueList
                = Stream
                    .of(Priority.values())
                    .map(priority -> new LinkedBlockingQueue<Function<Void, Void>>())
                    .collect(Collectors.toList());
        
        int baseCount = CPU_COUNT - 1;
        for (int i = 0; i < baseCount; i++) {
            int code = i % Priority.values().length;
            Priority priority = Priority.fromValue(code);

            Executor executor = this.executorMap.computeIfAbsent(i, k -> new Executor(queueList, priority.getValue() + 1));
            executor.run();
        }
        Stream.of(Priority.values()).forEach((priority) -> {
            int index = baseCount / Priority.values().length;
            
            System.out.println("Create executor index: " + index);
        });

    }

    private void initNormalSize() {
        for (int i = 0; i < Priority.values().length; i++) {
            queueList.add(new LinkedBlockingQueue<>());
        }
        for (Priority priority : Priority.values()) {
            Executor executor = this.executorMap.computeIfAbsent(priority.getValue(), v -> new Executor(queueList, priority.getValue() + 1));
            Thread t = new Thread(executor);
            t.start();
        }
    }


    public void attachAssignment(Assignment assignment, Priority priority, Object ...args) {
        if (closed) {
            System.out.println("Cannot attach assignment attached " + priority);
            return;
        }
        System.out.println("New assignment attached " + priority);
        
        BlockingQueue<Function<Void, Void>> queue = this.queueList.get(priority.getValue());
        Function<Void, Void> functor = x -> {
            assignment.process(args);
            return null;
        };
        queue.add(functor);
        // wake up workers
        for (int i = priority.getValue() * (CPU_COUNT - 1); i < Priority.values().length; i++) {
            Executor executor = this.executorMap.get(i);
            synchronized(executor) {
                executor.notify();
            }
        }
    }

    /**
     * Close entire thread dispatch
     */
    public void close() {
        this.closed = true;
        for(Executor executor: this.executorMap.values()) {
            executor.stop();
        }
    }
}
